<?php

namespace Plugins\AdminBasic\Http\Admin\Service\UpdateCURDFiles;

use App\Enums\Booleans;
use Plugins\AdminBasic\Http\Admin\Service\DevAuxService;
use Plugins\AdminBasic\Http\Admin\Service\MakeCURDFiles\MakeEnum;
use App\Models\Attributes\CreateSql;
use Carbon\Carbon;
use Hyperf\Stringable\Str;
use Sc\Util\ClassFile\ClassFileResolve;
use Sc\Util\ClassFile\Components\ClassFileConstruction;
use Sc\Util\ClassFile\Components\FunctionParam;
use Sc\Util\ClassFile\Components\Method;
use Sc\Util\ClassFile\Components\Out\ValueOut;

class UpdateModel
{

    public function __construct(private readonly string $modelPath,
                                private readonly string $classname,
                                private readonly array $currentFieldInfo
    ){
        ValueOut::registerReplace("/'delete_time'\s+=>\s+'int',\s+'update_time'\s+=>\s+'datetime:Y-m-d H:i:s',\s+'create_time'\s+=>\s+'datetime:Y-m-d H:i:s',/", '...self::DEFAULT_CASTS,');
    }


    public function update(bool $isView)
    {
        $model = ClassFileResolve::resolve($this->classname);
        $class = new ($this->classname)();
        $table = $class->getTable();

        $createSql = DevAuxService::aop()->getCreateSql($table);
        $docComment = $model->getDocBlock()->getDocComment();
        $currentFieldInfo = $this->currentFieldInfo;
        $docs = $this->propertyDoc($docComment);
        $model->getAttribute($model->getAppropriateClassName(CreateSql::class))?->setParam($createSql);

        foreach ($currentFieldInfo as $currentInfo) {
            $casts = $this->fieldType($currentInfo);
            if (!isset($docs[$currentInfo['field']])){
                $docComment[] = $currentInfo['doc'];
                $model->getProperty('fillable')->setDefault([
                    ...$model->getProperty('fillable')->getDefault(),
                    $currentInfo['field']
                ]);

                if ($casts) {
                    $model->getProperty('casts')->setDefault([
                        ...$model->getProperty('casts')->getDefault(),
                        $currentInfo['field'] => $casts,
                    ]);

                    if ($casts === 'Carbon' && !$model->hasClassUse(Carbon::class)){
                        $model->addUses(Carbon::class);
                    }
                }
            }else{
                $originField = $docs[$currentInfo['field']];
                if ($originField['field_type'] !== $currentInfo['field_type']) {
                    $default = $model->getProperty('casts')->getDefault();
                    if ($casts) {
                        $default[$currentInfo['field']] = $casts;
                    }else{
                        unset($default[$currentInfo['field']]);
                    }
                    $model->getProperty('casts')->setDefault($default);
                    foreach ($docComment as &$value) {
                        if (preg_match("/@property\s+\w+\s+\\\${$currentInfo['field']}/", $value)){
                            $value = $currentInfo['doc'];
                        }
                    }
                }

                if ($originField['comment'] !== $currentInfo['comment']) {
                    foreach ($docComment as &$value) {
                        if (preg_match("/@property\s+(\w+)\s+\${$currentInfo['field']}/", $value)){
                            $value = $currentInfo['doc'];
                        }
                    }
                }
            }

            if (!empty($currentInfo['status_map'])) {
                $enumName = $model->getName() . 'Enum' . Str::studly($currentInfo['field']);
                if (!$model->getClassMethod('get' . Str::studly($currentInfo['field']) . 'Attribute')){
                    $this->enumGetMethod($enumName, $currentInfo, $model);
                }
                if (!$model->getClassMethod('set' . Str::studly($currentInfo['field']) . 'Attribute')) {
                    $this->enumSetMethod($enumName, $currentInfo, $model);
                }
            }
        }

        $model->getDocBlock()->setDocComment($docComment);

        $code = preg_replace('/CreateSql\(<<<EOT(.*?)EOT\)/s', 'CreateSql(<<<SQL$1SQL)', $model->getCode());
        if ($isView) {
            return $code;
        }
        file_put_contents($this->modelPath, $code);
        return "model 已更新";
    }

    /**
     * @param array $docs
     * @return array
     */
    public function propertyDoc(array $docs): array
    {
        foreach ($docs as &$doc) {
            if (!preg_match('/@property\s+(\w+)\s+\$(\w+)(\s*.*)?$/m', $doc, $info)) {
                continue;
            }
            $doc = [
                'field' => $info[2],
                'field_type' => $info[1],
                'comment' => trim($info[3]),
                'doc' => $doc,
            ];
        }
        unset($doc);

        return array_column($docs, null, 'field');
    }

    /**
     * @param mixed $currentInfo
     * @return string
     */
    public function fieldType(mixed $currentInfo): string
    {
        $casts = '';
        if ($currentInfo['column_type'] === 'json' || str_ends_with($currentInfo['field'], '_ids')) {
            $casts = 'array';
        }
        if ($currentInfo['column_type'] === 'datetime') {
            $casts = 'datetime:Y-m-d H:i:s';
        }
        if ($currentInfo['column_type'] === 'date') {
            $casts = 'date:Y-m-d';
        }
        if ($currentInfo['column_type'] === 'timestamp') {
            $casts = 'timestamp';
        }
        if (str_contains($currentInfo['column_type'], 'decimal')) {
            $casts = 'float';
        }
        if (str_contains($currentInfo['column_type'], 'int') && empty($currentInfo['status_map'])) {
            $casts = 'int';
        }

        return $casts;
    }

    public function enumGetMethod(string $enumName, array $field, ClassFileConstruction $model): void
    {
        if (MakeEnum::fieldIsBoolean($field['status_map'])) {
            $enumName = "Booleans";
            $model->addUses(Booleans::class);
        }else{
            $allClassName = strtr($model->getNamespace(), ['\\Models' => '\\Enums']) . '\\' . $enumName;
            $model->addUses($allClassName);
        }

        $model->addMethods(function () use ($field, $enumName){
            $method = new Method("get" . Str::studly($field['field']) . 'Attribute');
            $method->addParameters(
                new FunctionParam("value")
            );

            $method->setReturnType($enumName);
            $method->addCode("return $enumName::from(\$value);");

            return $method;
        });
    }

    public function enumSetMethod(string $enumName, array $field, ClassFileConstruction $model): void
    {
        if (MakeEnum::fieldIsBoolean($field['status_map'])) {
            $enumName = "Booleans";
            $model->addUses(Booleans::class);
        }else{
            $allClassName = strtr($model->getNamespace(), ['\\Models' => '\\Enums']) . '\\' . $enumName;
            $model->addUses($allClassName);
        }

        $model->addMethods(function () use ($field, $enumName){
            $method = new Method("set" . Str::studly($field['field']) . 'Attribute');
            $method->addParameters(
                new FunctionParam("value")
            );
            $method->setReturnType('void');
            $method->addCode("\$this->attributes['{$field['field']}'] = \$value instanceof $enumName ? \$value->value : \$value;");

            return $method;
        });
    }
}